/**
 * Copyright (c) 2012 Nokia Corporation.
 */

package com.nokia.example.simplepurchase;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

import com.nokia.example.simplepurchase.uicomponents.AbstractStepIndicator;
import com.nokia.example.simplepurchase.uicomponents.Buttons;
import com.nokia.example.simplepurchase.uicomponents.ProgressBar;
import com.nokia.example.simplepurchase.uicomponents.StepIndicatorHi;
import com.nokia.example.simplepurchase.uicomponents.StepIndicatorLow;
import com.nokia.example.simplepurchase.uicomponents.TextWrapper;

import com.nokia.mid.payment.IAPClientPaymentException;
import com.nokia.mid.payment.IAPClientPaymentListener;
import com.nokia.mid.payment.IAPClientPaymentManager;
import com.nokia.mid.payment.IAPClientProductData;
import com.nokia.mid.payment.IAPClientUserAndDeviceData;

import com.nokia.mid.ui.VirtualKeyboard;

/**
 * IapCanvas is responsible for displaying the UI and controlling the
 * application logic. All the IAP related code resides in this class.
 */
public class IapCanvas
	extends Canvas
	implements IAPClientPaymentListener,
			   Buttons.Listener,
			   ProgressBar.Listener,
			   CommandListener
{
	// Constants
	private static final int DEFAULT_BACKGROUND_COLOR = 0xff666666;
	private static final int DEFAULT_FOREGROUND_COLOR = 0xffffffff;
	private static final int MARGIN = 6;
	private static final int PRODUCT_INFO_TIMEOUT = 20000; // Milliseconds

	// STEPS
	private static final int START = 1;
	private static final int RETRIEVING_PRODUCT_INFORMATION = 2;
	private static final int SHOW_PRODUCT_INFORMATION = 3;
	private static final int SHOW_RESULT = 4;

	// IAP
	private static final String PRODUCT_ID = "588547";

	// Members
	private Main _midlet = null;
	private AbstractStepIndicator _stepIndicator = null;
	private Buttons _buttons = null;
	private ProgressBar _progressBar = null;
	private TextWrapper _textWrapper = null;
	private String _infoText = null;
	private int _currentStep = 1;
	private final boolean _hasKeypad;

	// IAP
	private IAPClientPaymentManager _iapClientPaymentManager = null;
	
	// Commands for touch and type and non-touch
	private Command _exitCommand =  null;
	private Command _startCommand = null;
	private Command _buyCommand = null;
	private Command _doneCommand = null;
	private Command _leftCancelCommand = null;

	/**
	 * Constructor.
	 * @param midlet The app MIDlet.
	 */
	public IapCanvas(Main midlet) {
		_midlet = midlet;
		_hasKeypad = !hideVirtualKeypad();
		
		final int width;
		final int height;
		
		if (_hasKeypad) {
			// Use Commands on touch and type and non-touch devices
			_exitCommand =  new Command("Exit", Command.EXIT, 0);
			_startCommand = new Command("Start", Command.OK, 1);
			_buyCommand = new Command("Buy", Command.OK, 1);
			_doneCommand = new Command("Done", Command.OK, 1);
			_leftCancelCommand =  new Command("Cancel", Command.SCREEN, 1);
			addCommand(_exitCommand); // Exit is visible on every step
			setCommandListener(this);
			
            width = getWidth();
            height = getHeight();
		}
		else {
            setFullScreenMode(true);
            width = getWidth();
            height = getHeight();
    		_buttons = new Buttons(this, width, height);
		}
		
		if (height < 240) {
			_stepIndicator = new StepIndicatorLow(width, 4);
		}
		else {
			_stepIndicator = new StepIndicatorHi(width, 4);
		}
		
		_stepIndicator.setParent(this);
		_stepIndicator.setPosition(0, height - _stepIndicator.height());
		_stepIndicator.setVisible(true);
		
		_progressBar = new ProgressBar(width - MARGIN * 2, ProgressBar.HEIGHT, this);
		_progressBar.setParent(this);
		_progressBar.setPosition(MARGIN, height / 2);
		_textWrapper = new TextWrapper(Font.getDefaultFont(), width - MARGIN * 2);
		
		// Get IAP Client Payment Manager and start listening to its callbacks
		initIAPClientPaymentManager();
		
		setStep(START);
	}
	
	/**
	 * Methods (callbacks) from IAPClientPaymentListener.
	 */
	
	public void productDataListReceived(int arg0, IAPClientProductData[] arg1) {
		// Not implemented		
	}

	public void productDataReceived(int status, IAPClientProductData productData) {
		if (_currentStep == START || _currentStep == SHOW_RESULT) {
			// Cancelled?
			return;
		}
		
		StringBuffer buffer = new StringBuffer();
		buffer.append("Title: ").append(productData.getTitle()).append('\n');
		buffer.append("Short description: ").append(productData.getShortDescription()).append('\n');
		buffer.append("Long description: ").append(productData.getLongDescription()).append('\n');
		buffer.append("DRM protection: ").append(productData.getDrmProtection()).append('\n');
		buffer.append("Price: ").append(productData.getPrice());
		
		_progressBar.stop(100);
		_infoText = buffer.toString();
		
		setStep(SHOW_PRODUCT_INFORMATION);
	}

	public void purchaseCompleted(int status, String ticket) {
		if (_currentStep == START || _currentStep == SHOW_RESULT) {
			// Cancelled?
			return;
		}
		
        boolean success = (status == IAPClientPaymentListener.OK
        		|| status == IAPClientPaymentListener.RESTORABLE);
        
        StringBuffer buffer = new StringBuffer();
        
        if (success) {
        	buffer.append("Product purchased successfully!\n");
        }
        else {
        	buffer.append("Failed to purchase the product!\n");
        }
        
        buffer.append("Status: ").append(status).append('\n');
        buffer.append("Ticket: ").append(ticket).append('\n');
        
        _infoText = buffer.toString();
        
        setStep(SHOW_RESULT);		
	}

	public void restorableProductsReceived(int arg0, IAPClientProductData[] arg1) {
		// Not implemented
	}

	public void restorationCompleted(int status, String ticket) {
		if (_currentStep == START || _currentStep == SHOW_RESULT) {
			// Cancelled?
			return;
		}
		
        boolean success = (status == IAPClientPaymentListener.OK
        		|| status == IAPClientPaymentListener.RESTORABLE);
        
        StringBuffer buffer = new StringBuffer();
        
        if (success) {
        	buffer.append("Product restored successfully!\n");
        }
        else {
        	buffer.append("Failed to restore the product!\n");
        }
        
        buffer.append("Status: ").append(status).append('\n');
        buffer.append("Ticket: ").append(ticket).append('\n');
        
        _infoText = buffer.toString();
        
		setStep(SHOW_RESULT);		
	}

	public void userAndDeviceDataReceived(int arg0,
			IAPClientUserAndDeviceData arg1)
	{
		// Not implemented
	}
	
	/**
	 * From Canvas.
	 */
	protected void paint(Graphics graphics) {
		// Paint the background
		graphics.setColor(DEFAULT_BACKGROUND_COLOR);
		graphics.fillRect(0, 0, getWidth(), getHeight());
		
		// Paint the informational text
		graphics.setColor(DEFAULT_FOREGROUND_COLOR);
		
		if (_infoText != null && _infoText.length() > 0) {
			try {
				_textWrapper.setText(_infoText);
				int y = MARGIN * 2;

				while (_textWrapper.hasMoreLines()) {
					graphics.drawString(_textWrapper.nextLine(),
							MARGIN, y, Graphics.TOP | Graphics.LEFT);
					y += _textWrapper.lineHeight();
				}
			}
			catch (IllegalArgumentException e) {}
		}
		
		_stepIndicator.repaint();
		
		// Paint the buttons
		if (_buttons != null) {
			_buttons.paint(graphics);
		}
	}

	/**
	 * From Canvas.
	 */	
	protected void pointerPressed(int x, int y) {
		if (_buttons == null)
			return;
		
		_buttons.onPointerPressed(x, y); 
		repaint(); 
	}
	
	protected void pointerReleased(int x, int y) {
		if (_buttons == null)
			return;
		
		_buttons.onPointerReleased(x, y); 
		repaint(); 
	}

	protected void pointerDragged(int x, int y) { 
		if (_buttons == null)
			return;
		
		_buttons.onPointerPressed(x, y); 
		repaint(); 
	}
	
	/**
	 * From CommandListener.
	 */
	public void commandAction(Command command, Displayable displayable) {
		if (command.equals(_startCommand)
				|| command.equals(_buyCommand)
				|| command.equals(_doneCommand))
		{
			onButtonClicked(Buttons.POSITIVE_ACTION);
		}
		else if (command.equals(_leftCancelCommand)) {
			onButtonClicked(Buttons.NEGATIVE_ACTION);
		}
		else if (command.equals(_exitCommand)) {
			onButtonClicked(Buttons.EXIT);
		}
	}
	
	/**
	 * From Buttons.Listener.
	 */
	public void onButtonClicked(int id) {
		switch (id) {
		case Buttons.POSITIVE_ACTION: {
			switch (_currentStep) {
			case START: {
	            int errorCode = _iapClientPaymentManager.getProductData(PRODUCT_ID);
	            
	            if (errorCode == IAPClientPaymentManager.SUCCESS) {
	            	setStep(RETRIEVING_PRODUCT_INFORMATION);
	            }
	            else {
	            	// Show the error code
	            	_infoText = "IAPClientPaymentManager:: getProductData() failed with error code "
	            			+ errorCode;
	            	setStep(SHOW_RESULT);
	            }
	            
	            break;
			}
			case SHOW_PRODUCT_INFORMATION: {
				// Buy
		        int errorCode = _iapClientPaymentManager.purchaseProduct(PRODUCT_ID,
		        		IAPClientPaymentManager.FORCED_AUTOMATIC_RESTORATION);
		        
		        if (errorCode != IAPClientPaymentManager.SUCCESS) {
	            	// Show the error code
	            	_infoText = "IAPClientPaymentManager:: purchaseProduct() failed with error code "
	            			+ errorCode;
	            	setStep(SHOW_RESULT);
		        }
		        
				break;
			}
			case SHOW_RESULT: {
				// Done
				setStep(START);
			}
			}
			
			break;
		}
		case Buttons.NEGATIVE_ACTION: {
			setStep(START);
			break;
		}
		case Buttons.EXIT: {
			_midlet.exit();
		}
		}		
	}
	
	/**
	 * From ProgressBar.Listener.
	 */
	public void onTimeout() {
		_infoText = "Failed to retrieve product information. Please check your network connection.";
		setStep(SHOW_RESULT);
	}
	
	/**
	 * From ProgressBar.Listener.
	 */
	public void onValueChanged(int value) {
		repaint();
		serviceRepaints();
	}
	
	/**
	 * Gets the IAP client payment manager instance and starts to listening
	 * to its callbacks.
	 */
	private void initIAPClientPaymentManager() {
		try {
			_iapClientPaymentManager =
					IAPClientPaymentManager.getIAPClientPaymentManager();
		}
		catch (IAPClientPaymentException e) {
			_infoText = "Failed to get payment manager instance: " + e.toString();
			setStep(SHOW_RESULT);
		}
		
		IAPClientPaymentManager.setIAPClientPaymentListener(this);
	}
	
	/**
	 * Helper method for setting the current step. Updates the buttons and the
	 * step indicator based on the given step, and finally repaints the canvas.
	 * @param step The current step to set.
	 */
	private void setStep(final int step) {
		_currentStep = step;
		_progressBar.setVisible(false);

		if (_hasKeypad) {
			updateCommands();
		}
		else {
			updateButtons();
		}
		
		// Set the info text and the buttons
		switch (step) {
		case START: {
			if (_hasKeypad) {
				_infoText = "This application will demonstrate the flow of "
						+ "In-Application Purchasing. Press start to begin.";
			}
			else {
				_infoText = "Welcome to Simple Purchase example! This "
						+ "application will demonstrate the flow of In-Application "
						+ "Purchasing.\n\nTap start button to begin.";
			}
			
			break;
		}
		case RETRIEVING_PRODUCT_INFORMATION: {
			_infoText = "Retrieving product information. Please wait...";
			_progressBar.setVisible(true);
			_progressBar.start(PRODUCT_INFO_TIMEOUT);
			break;
		}
		case SHOW_PRODUCT_INFORMATION: {
			break;
		}
		case SHOW_RESULT: {
			break;
		}
		default: {
			System.out.println("Invalid step!");
			setStep(START);
			return;
		}
		}
		
		// Update the step indicator
		_stepIndicator.setCurrentStep(step);
		
		repaint();
	}
	
	/**
	 * Hides the virtual keypad on touch devices.
	 * @return True if the virtual keypad was hidden, false otherwise. Note that
	 * 		   if the method returns false, the app is running on non-touch
	 * 		   or touch and type device.
	 */
	private final boolean hideVirtualKeypad() {
		try {
            Class.forName("com.nokia.mid.ui.VirtualKeyboard");
            VirtualKeyboard.hideOpenKeypadCommand(true);
            return true;
        }
        catch (ClassNotFoundException e) {
        }

    	return false;
	}

	/**
	 * Updates the buttons according to the current step.
	 */
	private void updateButtons() {
		if (_buttons == null) {
			return;
		}
		
		switch (_currentStep) {
		case START: {
			_buttons.setButtons("Start", true, null, false);
			break;
		}
		case RETRIEVING_PRODUCT_INFORMATION: {
			_buttons.setButtons(null, false, "Cancel", true);
			break;
		}
		case SHOW_PRODUCT_INFORMATION: {
			_buttons.setButtons("Buy", true, "Cancel", true);
			break;
		}
		case SHOW_RESULT: {
			_buttons.setButtons("Done", true, null, false);
			break;
		}
		}
	}
	
	/**
	 * Updates the commands based on the current step.
	 */
	private void updateCommands() {
		// Remove the current commands
		removeCommand(_startCommand);
		removeCommand(_buyCommand);
		removeCommand(_doneCommand);
		removeCommand(_leftCancelCommand);
		
		switch (_currentStep) {
		case START: {
			addCommand(_startCommand);
			break;
		}
		case RETRIEVING_PRODUCT_INFORMATION: {
			addCommand(_leftCancelCommand);
			break;
		}
		case SHOW_PRODUCT_INFORMATION: {
			addCommand(_leftCancelCommand);
			addCommand(_buyCommand);
			break;
		}
		case SHOW_RESULT: {
			addCommand(_doneCommand);
			break;
		}
		}
	}
}
